//
//  conversion.c
//  imageToASCII
//
//  Created by Benno Lauther on 12.01.14.
//  Copyright (c) 2014 Benno Lauther. All rights reserved.
//

#include <stdio.h>
#include <string.h>
#include "prototypes.h"
#include <stdlib.h>

/**
 * getBlockSize
 *
 * Calculates the size of Blocks necessary to fit the Image into the outputSize.
 * The width will be shrinked to the output width.
 *
 *  @param  imageSize       Size of the image that shall be converted
 *  @param  outputSize      Size of the area the image shall Fit in
 *  @return Size*           Size of one Block;
 */

Size*       getBlockSize(Size* imageSize, Size* outputSize)
{
    Size* blockSize = (Size*)malloc(sizeof(Size));
    if(imageSize->width>outputSize->width)
	{
		if(imageSize->height>outputSize->width)
		{
			blockSize->width=imageSize->width/outputSize->width;
			blockSize->height=imageSize->height/outputSize->width;
		}
		else
		{
			blockSize->width=imageSize->width/imageSize->height;
			blockSize->height=1;
		}
	}
	else
	{
		blockSize->width=1;
		blockSize->height=1;
	}
    return blockSize;
}

/**
 * getAverageColor
 *
 * calculates the average color of a Block.
 *
 *  @param      image the image you work on
 *  @param      block the block you want to calculate, block.size in Pixels, block.origin in Characters
 *  @return
 */

Color*      getAverageColor(Image* image,Block* block)
{
    Pixel currentPixel;
    Color* averageColor = (Color*) malloc(sizeof(Color));
    int numberOfPixels = 0;
    uint64_t    redSum      =0;
    uint64_t    greenSum    =0;
    uint64_t    blueSum     =0;
    int blockSize = block->size.width*block->size.height;
    int imageSize = image->size.width*image->size.height;
#ifdef DEBUG_OUTPUT
    printf("Blocksize(%5d * %5d)=\t%d\nImagesize(%5d * %5d)=\t%d\n",block->size.width,block->size.height,blockSize,image->size.width,image->size.height,imageSize);

#endif
    //Set Index (Block-X|Block-Y)
    //Origin coordinates in characters -> pixel& 2dimensional -> 1dimensional
    int startIndex =    block->origin.y * image->size.width * block->size.height
                        + block->origin.x*block->size.width;
    int index = 0;
#ifdef DEBUG_OUTPUT
	printf("Just before looping through pixels of block to collect color data.\nstartIndex=%d\n",startIndex);
#endif
    //Collect data of the pixels in the block
    while(numberOfPixels<blockSize && index<imageSize)
    {
        index = startIndex +
                image->size.width * (numberOfPixels/block->size.width)
                + numberOfPixels%block->size.width;
        
        currentPixel = image->data[index];
        redSum      += currentPixel.r;
        greenSum    += currentPixel.g;
        blueSum     += currentPixel.b;
        numberOfPixels++;
    }
    averageColor->r =  redSum/numberOfPixels;
    averageColor->g =  greenSum/numberOfPixels;
    averageColor->b =  blueSum/numberOfPixels;

#ifdef DEBUG_OUTPUT
	printf("Average Color determined.\n\n");
#endif
    return averageColor;
}

/**
 * transformImageToAsciiPixel
 *
 * Transforms an image object to a colored ASCII-Pixel String.
 *
 *  @param  image       the image object you want to transform
 *  @param  outputSize  the size you want your ASCII Art to fit in (in chars)
 *  @return                  String containing the ASCII Art
 */
char*       transformImageToAsciiPixel(Image* image, Size* outputSize)
{
    if(image==NULL)
    {
        return NULL;
    }

    char* finalString = NULL;
    char* colorEscape;
    Size* blockSize;
    Block* currentBlock;
    Color* averageColor;
    char* resetColors ="\x1b[39;49m";
    
    int blockCountX;
    int blockCountY;
    int imagePixel = image->size.width*image->size.height;
    int pixelPerBlock;
    
    int blockIndex =0;
    int charIndex = 0;

    
    blockSize = getBlockSize(&image->size, outputSize);
    blockCountX = image->size.width/ blockSize->width;
    blockCountY = image->size.height/ blockSize->height;
    
    currentBlock = (Block*)malloc(sizeof(Block));
#ifdef DEBUG_OUTPUT
    printf("Allocated memory, initialized variables.\nblockSize->width=%d\nblockSize->height=%d\nblockCountX=%d\nblockCountY=%d\n",blockSize->width,blockSize->height,blockCountX,blockCountY);
#endif
    currentBlock->size = *blockSize;
    pixelPerBlock = blockSize->width*blockSize->height;
   
   
    finalString = (char*) malloc(sizeof(char) * (imagePixel / pixelPerBlock * 11 + blockCountY * 9 + 1) *10);
#ifdef DEBUG_OUTPUT
	printf("Just before final transforming loop.\n");
#endif	
    while(blockIndex<(imagePixel/pixelPerBlock))
    {
        
        //pixelIndex = blockIndex*blockSize->width;
        currentBlock->origin.x = blockIndex%blockCountX;
        currentBlock->origin.y = blockIndex/blockCountX;
        
        averageColor = getAverageColor(image, currentBlock);
        colorEscape = colorToAnsi(averageColor);
        
        for(int i=0; colorEscape[i]!='\0';i++, charIndex++)
        {
            finalString[charIndex] = colorEscape[i];
        }
        finalString[charIndex++] = '#';
        
        if((blockIndex + 1) % blockCountX == 0)
        {
            int i1=0;
            while(resetColors[i1]!='\0')
            {
                finalString[charIndex++]=resetColors[i1++];
            }
            finalString[charIndex++] = '\n';
        }
        blockIndex++;
    }
#ifdef DEBUG_OUTPUT
	printf("Final transformation: Done!\n");
#endif
    free(image);
    return finalString;
}

char*       transformImageToAsciiArtMonochrome(Image* image, Size* outputSize)
{
    if(image==NULL)
    {
        return NULL;
    }
    char* finalString = NULL;
    char asciiChar;
    Size* blockSize;
    Block* currentBlock;
    Color* averageColor;

    int blockCountX;
    int blockCountY;
    int imagePixel = image->size.width*image->size.height;
    int pixelPerBlock;

    int blockIndex =0;
    int charIndex = 0;


    blockSize = getBlockSize(&image->size, outputSize);
    blockCountX = image->size.width/ blockSize->width;
    blockCountY = image->size.height/ blockSize->height;

    currentBlock = (Block*)malloc(sizeof(Block));
#ifdef DEBUG_OUTPUT
    printf("Allocated memory, initialized variables.\nblockSize->width=%d\nblockSize->height=%d\nblockCountX=%d\nblockCountY=%d\n",blockSize->width,blockSize->height,blockCountX,blockCountY);
#endif
    currentBlock->size = *blockSize;
    pixelPerBlock = blockSize->width*blockSize->height;


    finalString = (char*) malloc(sizeof(char) * (imagePixel / (float)pixelPerBlock + blockCountY + 1) );
#ifdef DEBUG_OUTPUT
    printf("Just before final transforming loop.\n");
#endif
    while(blockIndex<(imagePixel/pixelPerBlock))
    {
        currentBlock->origin.x = blockIndex%blockCountX;
        currentBlock->origin.y = blockIndex/blockCountX;

        averageColor = getAverageColor(image, currentBlock);
        asciiChar = colorToAsciiArtMonochrome(averageColor);
        finalString[charIndex++] = asciiChar;
        if((blockIndex + 1) % blockCountX == 0)
        {
            finalString[charIndex++] = '\n';
        }

        blockIndex++;
    }
#ifdef DEBUG_OUTPUT
    printf("Final transformation: Done!\n");
#endif
    
    return finalString;
}


char        colorToAsciiArtMonochrome(Color* averageColor)
{
    uint8_t monoColor = (int) (averageColor->r * 0.2125) + (int) (averageColor->g * 0.7154) + (int) (averageColor->b * 0.0721);
    char asciiChar;
    if(monoColor == 0)
    {
        asciiChar = '#';
    }
    else if(monoColor < 25)
    {
        asciiChar = '8';
    }
    else if(monoColor < 50)
    {
        asciiChar = 'D';
    }
    else if(monoColor < 75)
    {
        asciiChar = '7';
    }
    else if(monoColor < 100)
    {
        asciiChar = 'c';
    }
    else if(monoColor < 125)
    {
        asciiChar = '/';
    }
    else if(monoColor < 150)
    {
        asciiChar = 'i';
    }
    else if(monoColor < 175)
    {
        asciiChar = '+';
    }
    else if(monoColor < 200)
    {
        asciiChar = '~';
    }
    else if(monoColor < 225)
    {
        asciiChar = '-';
    }
    else if(monoColor < 250)
    {
        asciiChar = '.';
    }
    else
    {
        asciiChar = ' ';
    }
    return asciiChar;
}

/**
 * colorToAnsi
 *
 * returning ansi-escape sequence for back- and frontcolor
 *
 *  @param  averageColor     Average color of current Block.
 *  @return                  ANSI-Escape sequence
 */
char*      colorToAnsi(Color* averageColor)
{
    char* ansiColor=NULL;
    unsigned int color, sumCurrentColor, sumClosestColor;
    sumClosestColor=-1;
    //initialize color as black
    color=0;
    //compare brown/yellow
    sumCurrentColor=averageColor->r-255+averageColor->g-255+averageColor->b;
    if(sumClosestColor<sumCurrentColor||(averageColor->b<100&&averageColor->r+averageColor->g>200))
    {
        sumClosestColor=sumCurrentColor;
        color=3;
    }
    //compare magenta
    sumCurrentColor=averageColor->r-255+averageColor->g+averageColor->b-255;
    if(sumClosestColor<sumCurrentColor||(averageColor->g<100&&averageColor->r+averageColor->b>200))
    {
        sumClosestColor=sumCurrentColor;
        color=5;
    }
    //compare cyan
    sumCurrentColor=averageColor->r+averageColor->g-255+averageColor->b-255;
    if(sumClosestColor<sumCurrentColor||(averageColor->r<100&&averageColor->g+averageColor->b>200))
    {
        sumClosestColor=sumCurrentColor;
        color=6;
    }
    //compare red
    sumCurrentColor=averageColor->r-255+averageColor->g+averageColor->b;
    if(sumClosestColor<sumCurrentColor||averageColor->r*0.8>averageColor->g+averageColor->b)
    {
        sumClosestColor=sumCurrentColor;
        color=1;
    }
    //compare green
    sumCurrentColor=averageColor->r+averageColor->g-255+averageColor->b;
    if(sumClosestColor<sumCurrentColor||averageColor->g*0.8>averageColor->r+averageColor->b)
    {
        sumClosestColor=sumCurrentColor;
        color=2;
    }
    //compare blue
    sumCurrentColor=averageColor->r+averageColor->g+averageColor->b-255;
    if(sumClosestColor<sumCurrentColor||averageColor->b*0.8>averageColor->r+averageColor->g)

    {
        sumClosestColor=sumCurrentColor;
        color=4;
    }
    //compare white/gray
    if(averageColor->r+averageColor->g+averageColor->b>500)
    {
        color=7;
    }
    //compare black
    if(averageColor->r+averageColor->g+averageColor->b<200)
    {
	color=0;
    }
    ansiColor=(char*)malloc(sizeof(char)*16+1);

    sprintf(ansiColor,"\x1b[%dm\x1b[%dm",color+30,color+40);

    return ansiColor;
}
